'''
@Project ：12306
@File    ：查询余票.py
@IDE     ：PyCharm 
@Author  ：子协
@Date    ：2025-04-29 22:39 
'''
import requests
import re
import json
import time
from datetime import datetime
from myemail import email

# 12306域名
domain_12306 = r'https://kyfw.12306.cn'
# 初始化url
url_init = r'/otn/leftTicket/init'
# 余票查询url 后边会根据初始化url的返回值进行对应修改
url_ypcx = r'/otn/'
# 车站名三字码url
url_stations_name = r"/otn/resources/js/framework/station_name.js"

# 座位类型对应的索引
chairType_index = {"高级软卧": 0,
                   "软卧":     1,
                   "一等卧":   1,
                   "软座":     2,
                   "特等座":   3,
                   "无座":     4,
                   "硬卧":     5,
                   "二等卧":   5,
                   "硬座":     6,
                   "二等座":   7,
                   "一等座":   8,
                   "商务座":   9,
                   "动卧":     10}
# 读取json配置文件
def ReadFromJSON(file_path):
    with open(file_path, mode='r', encoding='utf-8') as file:
        config = json.load(file)
    return config

# 获取余票查询接口的URL地址
def GetLeftTicketURL():
    global url_ypcx
    res = requests.get(domain_12306 + url_init)
    # 匹配可能包含空格和换行的模式
    pattern = r"var\s+CLeftTicketUrl\s*=\s*'(.*?)'\s*;"
    match = re.search(pattern, res.text, re.DOTALL)
    if match:
        url = match.group(1).strip()
        url_ypcx += url
        # print("提取到的URL:", url)
    else:
        print("错误:获取余票查询接口URL失败")

# 获取所有的三字码
def GetAllCode():
    station_code = {}
    res = requests.get(domain_12306 + url_stations_name)
    stations_name = res.text
    stations_name = stations_name[20:]      # 去除掉前边的  var station_names ='
    stations_name = stations_name[:-2]      # 去除掉最后的  ':
    stations = stations_name.split('@')[1:]  # 以@划分并去掉第一个空元素
    for station in stations:
        station_msg = station.split('|')     # 以 | 划分
        name = station_msg[1]                # 第一个元素为 车站名
        code = station_msg[2]                # 第二个元素为 车站名对应的三字码
        station_code[name] = code
    return station_code

# 根据车站名字获取对应的三字码
def GetCodeByName(station_name,station_code):
    if station_name not in station_code:
        return -1
    return station_code[station_name]

# 解码返回的数据
def Decode(string):
    _string = string.split('|')
    # 0:车次 1:出发时间 2:到达时间 3:高级软卧 4:软卧/一等卧 5:软座 6:特等座 7:无座 8:硬卧/二等卧 9:硬座 10:二等座 11:一等座 12:商务座 13:动卧
    result = (_string[3],_string[8],_string[9],_string[21],_string[23],_string[24],_string[25],_string[26],_string[28],_string[29],_string[30],_string[31],_string[32],_string[33])
    return result

# 获取所有车次的信息
def GetAllTrains(from_station_code,to_station_code,date):
    trains_list = []
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
        "Connection": "keep-alive",
        "Content-Type": "application/json;charset=UTF-8",
        "Cookie": "CLIENT=zgzfw"
    }

    params = {"leftTicketDTO.train_date": date,
              "leftTicketDTO.from_station": from_station_code,
              "leftTicketDTO.to_station": to_station_code,
              "purpose_codes": "ADULT"
    }
    res = requests.get(domain_12306 + url_ypcx,headers=headers,params=params)
    print(res.text)
    try:
        trains_json = json.loads(res.text)
        trains = trains_json["data"]["result"]

        for train_info in trains:
            decode_msg = Decode(train_info)
            # print("一等座:{},二等座:{},无座:{}".format(decode_msg[chairType_index["一等座"]+3],decode_msg[chairType_index["二等座"]+3],decode_msg[chairType_index["无座"]+3]))
            trains_list.append(decode_msg)
        return trains_list
    except:
        print("错误:获取余票信息失败")
        return -1

# 获取指定车次的信息
def GetTrainByTrainNumber(train_number_list,trains_list):
    trains = []        # 符合条件的车次
    for train_info in trains_list:
        if train_info[0] in train_number_list:
            trains.append(train_info)
    if len(trains) == 0:
        return -1
    return trains

# 获取指定车次指定座位类型的余票信息
def GetLeftTicketByChairType(chair_type_list,trains_list):
    left_ticket_list = []   # 余票信息
    for train_info in trains_list:
        left_ticket = []
        for chair_type in chair_type_list:
            left_ticket.append(train_info[chairType_index[chair_type]+3])
        left_ticket_list.append((train_info[0],train_info[1],left_ticket))  # (车次，出发时间，[座位对应的余票信息])
    return left_ticket_list

# 判断是否有余票
def HasAvailableTickets(left_ticket_list):
    for train_info in left_ticket_list:
        for left_ticket in train_info[2]:
            if left_ticket!='无' and left_ticket!='':
                return train_info
    return 0

# 判断是否到截至时间
def CheckDateReminder(left_ticket_list,mouth_day,hours_before):
    current_time_s = time.time()                                            # 当前时间戳(S)
    hours_before_s = hours_before * 3600                                  # 截至时间(S)
    for train_info in left_ticket_list:
        departure_str = mouth_day + ' ' + train_info[1]
        departure_struct = time.strptime(departure_str, "%Y-%m-%d %H:%M")
        # print(departure_struct)
        departure_timestamp = time.mktime(departure_struct)                 # 火车出发时间的时间戳(S)
        # print(departure_timestamp)
        if current_time_s >= (departure_timestamp - hours_before_s):        # 当前已经达到截至时间
            return train_info                                               # 返回到达截至时间的火车信息
    return 0                                                                # 都没有到达截至时间

if __name__ == '__main__':
    config = ReadFromJSON(r"./config.json")
    from_station = config['from_station']
    to_station = config['to_station']
    date = config['date']
    hours_before = config['hours_before']
    chair_type_list = config['chair_type_list']
    train_number_list = config['train_number_list']
    from_email_name = config['from_email_name']
    from_email_account = config['from_email_account']
    from_email_token = config['from_email_token']
    to_email_name = config['to_email_name']
    to_email_account = config['to_email_account']
    email_topic = config['email_topic']
    # print(from_station,to_station,date,deadline_hour,chair_type,train_number_list)
    myemail = email(from_email_name,from_email_account,from_email_token)
    GetLeftTicketURL()
    station_code = GetAllCode()
    from_station_code = GetCodeByName(from_station,station_code)
    to_station_code = GetCodeByName(to_station, station_code)
    if from_station_code==-1 or to_station_code==-1:
        print("错误：未找到车站信息!")
    else:
        while True:
            trains_list = GetAllTrains(from_station_code,to_station_code,date)
            # print(trains_list)
            trains = GetTrainByTrainNumber(train_number_list,trains_list)
            # print(trains)
            if trains == -1:
                print("错误：没有获取到相关车次信息")
                break
            else:
                print("获取车次信息成功")
                left_ticket_list = GetLeftTicketByChairType(chair_type_list,trains)     # 获取指定车次信息
                print(left_ticket_list)
                available_tickets = HasAvailableTickets(left_ticket_list)               # 获取指定车次指定座位类型的信息
                if available_tickets!=0:                                                # 有票了
                    msg = f"车次:{available_tickets[0]} | 出发时间:{available_tickets[1]}"
                    for chair_type,tickets in zip(chair_type_list,available_tickets[2]):
                        msg += f" | {chair_type}:{tickets}"
                    print("有票了! "+msg)
                    res = myemail.login()
                    if res == -1:
                        print("登录失败")
                    else:
                        print("登录成功")
                        res = myemail.sendText(to_email_name,to_email_account,email_topic[0],msg)
                        if res == -1:
                            print("发送失败")
                        else:
                            print("发送成功")
                        myemail.quit()
                        break
                else:                                                                   # 无票
                    timeout_train = CheckDateReminder(left_ticket_list,date,hours_before)
                    if timeout_train != 0:                                              # 有车次到达了截至时间
                        msg = f"车次:{timeout_train[0]} 已到达截至时间,停止查询"
                        print(msg)
                        train_number_list.remove(timeout_train[0])                      # 移除到达截至时间的车次
                        # print(train_number_list)
                        res = myemail.login()
                        if res == -1:
                            print("登录失败")
                        else:
                            print("登录成功")
                            res = myemail.sendText(to_email_name,to_email_account,email_topic[1],msg)
                            if res == -1:
                                print("发送失败")
                            else:
                                print("发送成功")
                            myemail.quit()
                        if len(train_number_list) == 0:
                            msg = f"很遗憾,所有车次均已到达截至时间>.<"
                            print(msg)
                            res = myemail.login()
                            if res == -1:
                                print("登录失败")
                            else:
                                print("登录成功")
                                res = myemail.sendText(to_email_name, to_email_account, email_topic[1], msg)
                                if res == -1:
                                    print("发送失败")
                                else:
                                    print("发送成功")
                                myemail.quit()
                            break
                    else:
                        print("还是没票>.<,再等等吧")
            time.sleep(60)  # 暂停1分钟